package com.google.bitcoin.examples;

import com.google.bitcoin.core.*;
import com.google.bitcoin.discovery.DnsDiscovery;
import com.google.bitcoin.discovery.IrcDiscovery;
import com.google.bitcoin.discovery.SeedPeers;
import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.store.BoundedOverheadBlockStore;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;

import java.io.File;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutionException;

/**
 * @author apetersson
 */
public class MultisigWallet {

    private final NetworkParameters network;
    private final Wallet wallet;
    private final ECKey singleKey;
    private final ECKey key1;
    private final ECKey key2;

    public static void main(String[] args) throws AddressFormatException {
        NetworkParameters network = NetworkParameters.prodNet();
        new MultisigWallet(new ArmoryImporter(network), network).run();
    }

    public MultisigWallet(ArmoryImporter armoryImporter, NetworkParameters network) {
        this.network = network;
        ImmutableList<ECKey> keys = armoryImporter.importFile("multisig_test.keys.properties");
        Preconditions.checkState(keys.size() == 3);
        key1 = keys.get(0);
        key2 = keys.get(1);
        singleKey = keys.get(2);
        wallet = new Wallet(network);
        wallet.addKey(key1);
        wallet.addKey(key2);
        wallet.addKey(this.singleKey);
        if (!"1QHrEqNzY2WEfsWcG54WxxNfjKAuHZzuTK".equals(key1.toAddress(network).toString())) {
            throw new IllegalStateException();
        }
        if (!"12vBmpknJPH331wgy4JabMUmnm6HdCEW5r".equals(key2.toAddress(network).toString())) {
            throw new IllegalStateException();
        }
        if (!"19yCtDfiGGvoJJxYx5w7ZQTfPnvkcTuJHD".equals(singleKey.toAddress(network).toString())) {
            throw new IllegalStateException();
        }
    }

    private void run() {
        try {
            final PeerGroup peerGroup = new PeerGroup(network, new BlockChain(network, wallet, new BoundedOverheadBlockStore(network, new File("multisigChain.dat"))));
            peerGroup.addEventListener(new AbstractPeerEventListener(){
                @Override
                public void onPeerConnected(Peer peer, int peerCount) {
                    super.onPeerConnected(peer, peerCount);    //To change body of overridden methods use File | Settings | File Templates.
                    peer.addEventListener(new AbstractPeerEventListener(){
                        @Override
                        public void onTransaction(Peer peer, Transaction t) {
                            super.onTransaction(peer, t);    //To change body of overridden methods use File | Settings | File Templates.
                            try {
                                if (wallet.isTransactionRelevant(t,true)){
                                    forwardToMultisig(wallet, t, peerGroup);
                                }
                            } catch (ScriptException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    });
                }
            });
            wallet.addEventListener(new AbstractWalletEventListener() {
                @Override
                public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
                    super.onTransactionConfidenceChanged(wallet, tx);

                }

                @Override
                public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
                    super.onCoinsReceived(wallet, tx, prevBalance, newBalance);
                    forwardToMultisig(wallet, tx, peerGroup);
                }
            });
            peerGroup.setFastCatchupTimeSecs(wallet.getEarliestKeyCreationTime());
            peerGroup.addAddress(new PeerAddress(InetAddress.getLocalHost()));
            peerGroup.addPeerDiscovery(new DnsDiscovery(network));
            peerGroup.addPeerDiscovery(new SeedPeers(network));
            peerGroup.addPeerDiscovery(new IrcDiscovery("#bitcoin"));
            peerGroup.setMaxConnections(8);
            peerGroup.start();
            peerGroup.downloadBlockChain();
        } catch (BlockStoreException e) {
            throw new RuntimeException(e);
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    private void forwardToMultisig(Wallet wallet, Transaction tx, PeerGroup peerGroup) {
        for (TransactionOutput transactionOutput : tx.getOutputs()) {
            try {
                if (transactionOutput.getScriptPubKey().getToAddress().equals(singleKey.toAddress(network))) {
                    Transaction send = new Transaction(network);
                    send.addOutput(tx.getValueSentToMe(wallet), MultisigPubkeys.from(key1, key2, 2));
                    send.addInput(transactionOutput);
                    wallet.completeTx(send);
                    ListenableFuture<Transaction> future = peerGroup.broadcastTransaction(send);
                    Transaction transaction = future.get();
                    Preconditions.checkState(transaction.getOutputs().get(0).getScriptPubKey().isSentToMultisig());
                }
            } catch (ScriptException e) {
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private ECKey keyFromArmoryExport(String arg) {
        try {
            return new DumpedPrivateKey(network, arg.replace(" ", "")).getKey();
        } catch (AddressFormatException e) {
            throw new RuntimeException(e);
        }
    }
}
